闲话 22.7.29

闲话

这篇闲话我也没想29号写完。但是既然写了那就写完吧。
大概明天会写一下进阶的东西 其实都很水

都多久了我怎么还会唱无梦之梦 但是词忘得挺严重的。
完全记不得后面的词了。
发现似乎月光熟悉了点 我不知道为什么 srds超级春卷饭时间那段还是一点都不记得

感觉现在更能把一个东西抽象成一种类似算法的东西了
但是这种套的板子一般都比正解多几个log
全局平衡二叉树就是一个我不会的东西

写树剖了。 二十几分钟写完也没怎么调就a了。
我倒是想就这么a。 为什么用了Fast I/O以后stdin里面就没东西了啊
为什么scanf读不进来字符串啊
然后爆了个0
行吧我记住了

感觉什么东西上了线段树都只用加个log
如果你需要所有情况的解的话可以考虑直接按询问时间上线段树
然后在线段树上搜索
暴力枚举
然后加个log就好

单调栈处理最值

很多时候我们会在题里发现一些关于区间贡献是最值的问题。来一道题:

给定长为 \(n\) 的序列 \(a\),并将其所有连续子段内(共 \(\frac {n(n-1)} 2\) 个)的最大值插入一个可重集 \(S\)\(q\) 次询问,共三种:

  1. 询问可重集内所有小于 \(k\) 的数的数量
  2. 询问可重集内所有等于 \(k\) 的数的数量
  3. 询问可重集内所有大于 \(k\) 的数的数量

\(n, q \ \le 1e5, a[i]\ \le 1e9\)

此类问题的优化方式在于,所有子段对答案的贡献只有 \(O(n)\) 种,即原序列中不同的值。这样,我们可以枚举每个数 \(a_i\) 作为最大值出现的区间,记为 \([l_i, r_i]\)。当选择的段为 \([l,r]\),其中 \(l_i \le l \le i\)\(i \le r \le r_i\) 时,我们能保证此段的最大值为 \(a_i\)。这样,每个位置对答案的贡献即为 \((i - l_i + 1) \times (r_i - i + 1)\),使用树状数组维护即可。

好水啊

然后说一下如何产生 \([l_i,r_i]\)。你当然可以用set,但是这种单次对数的退化算法当然不是我要讲的东西。先说左端点,右端点类似。想想我们要维护什么?前缀里比他大的数。把前缀插进一个单调栈,再用它自己更新一下栈,最后栈顶元素所在位置就是左端第一个大于它的元素。位置+1记录即可。

top = 0;
rep(i,1,n) {
	while (top > 0 and a[stk[top]] < a[i]) top--;
	l[i] = (top == 0 ? 1 : stk[top] + 1);
	stk[++top] = i;
}
top = 0;
pre(i,n,1) {
	while (top > 0 and a[stk[top]] <= a[i]) top--;
	r[i] = (top == 0 ? n : stk[top] - 1);
	stk[++top] = i;
} 

但是扫两次常数太大了。怎么办?考虑弹栈的意义。我们倒着想。用当前元素来更新栈其实就等于栈内被弹出元素在倒着扫时把当前元素更新。有点绕。看代码就行。其实就是反了一下。

a[0] = a[n+1] = n+1; q[++top] = 0;
rep(i,1,n+1) {
	while (a[stk[top]] < a[i]) r[stk[top]] = i, top--;
	l[i] = stk[top]; q[++top] = i;
} 
posted @ 2022-07-28 20:29  joke3579  阅读(38)  评论(1编辑  收藏  举报